package com.springboot.file.util.easyexcel.strategy;

import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.CellData;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import com.springboot.file.util.BufferUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.poi.ss.usermodel.Cell;
import org.springframework.util.CollectionUtils;

import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 表头宽度根据数据内容自适应
 */
@Slf4j
public class CustomWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {
    private static final int COLUMN_WIDTH_BASE = 255;
    private final Map<Integer, Map<Integer, Double>> cache = new HashMap<>(8);

    /**
     * 设置列宽
     * 该方法覆盖了父类的方法，用于自定义列宽设置逻辑
     *
     * @param writeSheetHolder 写入sheet持有者
     * @param cellDataList 数据列表
     * @param cell 单元格
     * @param head 表头
     * @param relativeRowIndex 相对行索引
     * @param isHead 是否是表头
     */
    @Override
    protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell, 
                                  Head head, Integer relativeRowIndex, Boolean isHead) {
        // 判断是否需要设置列宽
        if (!isHead && CollectionUtils.isEmpty(cellDataList)) {
            return;
        }
        
        // 获取当前sheet的最大列宽缓存
        final int sheetNo = writeSheetHolder.getSheetNo();
        Map<Integer, Double> maxColumnWidthMap = cache.computeIfAbsent(sheetNo, k -> new HashMap<>(16));
        
        // 获取当前单元格的列索引
        final int columnIndex = cell.getColumnIndex();
        
        // 计算当前单元格的数据长度
        final double columnWidth = dataLength(cellDataList, cell, isHead);
        
        // 如果是数据行且有有效宽度
        if (!isHead && columnWidth >= 0) {
            // 获取当前列的最大宽度
            Double maxColumnWidth = maxColumnWidthMap.get(columnIndex);
            
            // 更新最大宽度
            if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                maxColumnWidthMap.put(columnIndex, columnWidth);
                
                // 立即应用列宽设置
                int finalWidth = (int)((columnWidth + 10) * COLUMN_WIDTH_BASE);
                writeSheetHolder.getSheet().setColumnWidth(columnIndex, finalWidth);
            }
        }
        
        // 如果是表头行，也设置列宽
        if (isHead && columnWidth >= 0) {
            int headerWidth = (int)((columnWidth + 5) * COLUMN_WIDTH_BASE);
            writeSheetHolder.getSheet().setColumnWidth(columnIndex, headerWidth);
        }
    }

    /**
     * 获取单元格数据长度
     * @param cellDataList 数据列表
     * @param cell 单元格
     * @param isHead 是否是表头
     * @return 单元格数据长度
     */
    private double dataLength(List<WriteCellData<?>> cellDataList, Cell cell, boolean isHead) {
        if (isHead) {
            return cell.getStringCellValue().getBytes().length;
        } else {
            CellData<?> cellData = cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return -1;
            } else {
                switch (type) {
                    case STRING:
                        return getExcelWidth(cellData.getStringValue());
                    case BOOLEAN:
                        return getExcelWidth(cellData.getBooleanValue().toString());
                    case NUMBER:
                        return getExcelWidth(cellData.getNumberValue().toString());
                    default:
                        return -1;
                }
            }
        }
    }

    /**
     * 调整单元格字符字节宽度，easyExcel默认直接用的UTF-8的byte长度，导致一旦三字节的字符过多就会变得很宽，一字节的字符过多就会不够宽
     * @param str 要计算宽度的字符串
     * @return 计算后的Excel宽度
     */
    private double getExcelWidth(String str) {
        if (str == null || str.isEmpty()) {
            return 0.0;
        }
        
        double length = 0.0;
        // 预分配足够大小的数组，减少CharBuffer扩容操作
        char[] buffer = new char[1];
        CharBuffer charBuffer = CharBuffer.allocate(1);
        
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            buffer[0] = c;
            BufferUtil.clear(charBuffer);
            charBuffer.put(buffer);
            BufferUtil.flip(charBuffer);
            
            ByteBuffer byteBuffer = StandardCharsets.UTF_8.encode(charBuffer);
            int byteLength = byteBuffer.remaining();
            
            // 根据字符的UTF-8编码长度调整Excel宽度
            if (byteLength == 1) {
                length += 1.05; // 单字节字符
            } else {
                length += 1.15; // 多字节字符
            }
        }
        
        return length;
    }
}